using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Timers;

namespace SmokeAlarm
{
    public class SmokeClient
    {
		public delegate void SmokeEventDelegate(SmokeClient client, string nick);
		public delegate void SmokeErrorDelegate(SmokeClient client, Exception e);
        public SmokeClient(IPEndPoint ipe)
        {
			m_socket = ConnectSocket(ipe);

			m_timer = new Timer();
			m_timer.Elapsed += new ElapsedEventHandler(this.SendPing);
			m_timer.Interval = 60*1000; // 1 minute
			m_timer.Enabled = true;
			m_timer.AutoReset = true;

            m_socket.BeginReceive(m_lengthBuff, 0, 4, SocketFlags.None, new AsyncCallback(ReceiveLengthCallback), this);
        }

		private void SendPing(object source, ElapsedEventArgs e)
		{
			SendRequest(System.Windows.Forms.SystemInformation.UserName + "/ping");
		}

		public void SetCallback(DummyForm callback)
		{
			m_callback = callback;
		}

        private static Socket ConnectSocket(IPEndPoint ipe)
        {
            Socket s = null;
            Socket tempSocket = 
                new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            tempSocket.Connect(ipe);

            if(tempSocket.Connected)
            {
                s = tempSocket;
            }

	        return s;
        }

		public void SendRequest(string request)
		{
			try
			{
			    // get string length
			    int reqLen = request.Length;
    
			    // get string length value into a byte array -- for use with
			    // Socket.Send
			    byte[] reqLenArray = BitConverter.GetBytes(reqLen);
    
			    // send the length value
			    m_socket.Send(reqLenArray, 4, System.Net.Sockets.SocketFlags.None);
    
			    // copy string to a byte array
			    byte[] dataArray = Encoding.ASCII.GetBytes(request);
    
			    // send the string array
			    m_socket.Send(dataArray, reqLen,
					       System.Net.Sockets.SocketFlags.None);
			}
			catch (Exception e)
			{
				m_callback.BeginInvoke(new SmokeErrorDelegate(m_callback.Error), this, e);
			}
		}

        private void ReceiveLengthCallback(IAsyncResult result)
        {
			try
			{
			    uint m_messageLength = BitConverter.ToUInt32(m_lengthBuff, 0);
			    m_buff = new byte[m_messageLength];
			    m_socket.BeginReceive(m_buff, 0, (int)m_messageLength, SocketFlags.None, new AsyncCallback(ReceiveMessageCallback), this);
            }
			catch (Exception e)
			{
				m_callback.BeginInvoke(new SmokeErrorDelegate(m_callback.Error), this, e);
			}
        }

        private void ReceiveMessageCallback(IAsyncResult result)
        {
			try
			{
			    string message = Encoding.ASCII.GetString(m_buff, 0, m_buff.Length);
			    int index = message.IndexOf('/');
			    string nick = message.Substring(0, index);
			    string command = message.Substring(index + 1, message.Length - index - 1);
			    switch (command)
			    {
				    case "smoke":
					    m_callback.BeginInvoke(new SmokeEventDelegate(m_callback.Smoke), this, nick);
					    break;
				    case "yes":
					    m_callback.BeginInvoke(new SmokeEventDelegate(m_callback.Yes), this, nick);
					    break;
				    case "no":
					    m_callback.BeginInvoke(new SmokeEventDelegate(m_callback.No), this, nick);
					    break;
				    case "holdon":
					    m_callback.BeginInvoke(new SmokeEventDelegate(m_callback.HoldOn), this, nick);
					    break;
				    case "ping":
					    // ignore
					    break;
				    default:
					    break;
			    }
                m_socket.BeginReceive(m_lengthBuff, 0, 4, SocketFlags.None, new AsyncCallback(ReceiveLengthCallback), this);
            }
			catch (Exception e)
			{
				m_callback.BeginInvoke(new SmokeErrorDelegate(m_callback.Error), this, e);
			}
        }

		private DummyForm m_callback;
        private Socket m_socket;
		private Timer m_timer;
        private byte[] m_buff = null;
		private byte[] m_lengthBuff = new byte[4] { 0, 0, 0, 0 };
    }
}
